package com.ely.coupon.common.lock;

import com.ely.coupon.common.lock.annotation.DistributedLocked;
import com.ely.coupon.common.lock.enums.LockType;
import com.ely.coupon.common.lock.enums.ReadWriteLockType;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.redisson.api.RLock;
import org.redisson.api.RReadWriteLock;
import org.redisson.api.RedissonClient;
import org.springframework.stereotype.Component;
import org.springframework.util.ObjectUtils;

import javax.annotation.Resource;
import java.lang.reflect.Method;

/**
 * 定义切面
 */
@Aspect
@Slf4j
public class DistributedLockedAop {
    @Resource
    private RedissonClient redissonClient;
    public static final String LOCKED_DISTRIBUTED_PREFIX = "locked:distributed:";
    public static final String DEFAULT_LOCKED_DISTRIBUTED_NAME = "default:";

    @Pointcut("@annotation(com.ely.coupon.common.lock.annotation.DistributedLocked)")
    public void pointCut() {
    }

    /**
     * 1. 控制核心方法是否执行
     * 2. 获取核心方法
     * 3. 可以核心方法的参数
     * 4. 可以获取方法的类信息
     *
     * @param pjp
     * @return
     */
    @Around("pointCut()")
    public Object around(ProceedingJoinPoint pjp) {
        Object proceed = null;
        Signature signature = pjp.getSignature();
        if (signature instanceof MethodSignature) {
            MethodSignature methodSignature = (MethodSignature) signature;
            Method method = methodSignature.getMethod();
            DistributedLocked distributedLocked = method.getAnnotation(DistributedLocked.class);
            RLock lock = getRLock(distributedLocked);
            try {
                boolean res = lock.tryLock(distributedLocked.waitTime(), distributedLocked.leaseTime(), distributedLocked.unit());
                if (res) {
                    proceed = pjp.proceed();
                }
                // 执行核心方法
            } catch (Throwable e) {
                throw new RuntimeException(e);
            } finally {
                lock.unlock();
            }
        }
        return proceed;
    }


    private String getLockedName(DistributedLocked distributedLocked) {
        return String.format("%s%s", LOCKED_DISTRIBUTED_PREFIX, ObjectUtils.isEmpty(distributedLocked.value()) ? DEFAULT_LOCKED_DISTRIBUTED_NAME : distributedLocked.value());
    }

    /**
     * 获取锁的类型
     *
     * @param distributedLocked
     * @return
     */
    private RLock getRLock(DistributedLocked distributedLocked) {
        RLock rLock = null;
        LockType type = distributedLocked.type();
        String lockedName = getLockedName(distributedLocked);
        switch (type) {
            case FAIR_LOCK:
                rLock = redissonClient.getFairLock(lockedName);
                break;
            case READ_WRITE_LOCK:
                RReadWriteLock readWriteLock = redissonClient.getReadWriteLock(lockedName);
                rLock = distributedLocked.readWriteType().equals(ReadWriteLockType.READ_LOCK) ? readWriteLock.readLock() : readWriteLock.writeLock();
                break;
            default:
                rLock = redissonClient.getLock(lockedName);
                break;
        }
        return rLock;
    }
}
